/**
 * Copyright (C) 2011 Ron Coleman. Contact: ronncoleman@gmail.com
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package bj.actor
import bj.card.Card
import scala.collection.mutable.HashMap
import bj.card.Hand
import bj.util.Config
import scala.actors.Actor._

/**
 * This class implements the functionality of a player using the Basic Strategy
 *
 * @param name Name of the player
 * @param bankroll Bankroll of the player
 * @param betAmt Minimum amount the player will bet
 */
class BetterPlayer(name: String, var bankRoll: Double, var betAmount: Double) extends Player(name, bankRoll, betAmount) {
  // Table containing basic strategy
  val basicStrategy = Map(
    // Map keys are player's hand value or cards (ranked in order)
    // Upcard: 0    A    2    3    4    5    6    7    8    9   10	
    2 -> List("", "H", "H", "H", "H", "H", "H", "H", "H", "H", "H"),
    3 -> List("", "H", "H", "H", "H", "H", "H", "H", "H", "H", "H"),
    4 -> List("", "H", "H", "H", "H", "H", "H", "H", "H", "H", "H"),
    5 -> List("", "H", "H", "H", "H", "H", "H", "H", "H", "H", "H"),
    6 -> List("", "H", "H", "H", "H", "H", "H", "H", "H", "H", "H"),
    7 -> List("", "H", "H", "H", "H", "H", "H", "H", "H", "H", "H"),
    8 -> List("", "H", "H", "H", "H", "H", "H", "H", "H", "H", "H"),
    9 -> List("", "H", "H", "D", "D", "D", "D", "H", "H", "H", "H"),
    10 -> List("", "H", "D", "D", "D", "D", "D", "D", "D", "D", "H"),
    11 -> List("", "H", "D", "D", "D", "D", "D", "D", "D", "D", "D"),
    12 -> List("", "H", "H", "H", "S", "S", "S", "H", "H", "H", "H"),
    13 -> List("", "H", "S", "S", "S", "S", "S", "H", "H", "H", "H"),
    14 -> List("", "H", "S", "S", "S", "S", "S", "H", "H", "H", "H"),
    15 -> List("", "H", "S", "S", "S", "S", "S", "H", "H", "H", "H"),
    16 -> List("", "H", "S", "S", "S", "S", "S", "H", "H", "H", "H"),
    17 -> List("", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S"),
    18 -> List("", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S"),
    19 -> List("", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S"),
    20 -> List("", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S"),
    21 -> List("", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S"),
    // Player's cards (ranked in order)
    List(1, 10) -> List("", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S"),
    List(1, 9) -> List("", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S"),
    List(1, 8) -> List("", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S"),
    List(1, 7) -> List("", "H", "S", "D", "D", "D", "D", "S", "S", "H", "H"),
    List(1, 6) -> List("", "H", "H", "D", "D", "D", "D", "H", "H", "H", "H"),
    List(1, 5) -> List("", "H", "H", "H", "D", "D", "D", "H", "H", "H", "H"),
    List(1, 4) -> List("", "H", "H", "H", "D", "D", "D", "H", "H", "H", "H"),
    List(1, 3) -> List("", "H", "H", "H", "H", "D", "D", "H", "H", "H", "H"),
    List(1, 2) -> List("", "H", "H", "H", "H", "D", "D", "H", "H", "H", "H"),
    List(1, 1) -> List("", "SP", "SP", "SP", "SP", "SP", "SP", "SP", "SP", "SP", "SP"),
    List(10, 10) -> List("", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S"),
    List(9, 9) -> List("", "S", "SP", "SP", "SP", "SP", "SP", "S", "SP", "SP", "S"),
    List(8, 8) -> List("", "SP", "SP", "SP", "SP", "SP", "SP", "SP", "SP", "SP", "SP"),
    List(7, 7) -> List("", "H", "SP", "SP", "SP", "SP", "SP", "SP", "H", "H", "H"),
    List(6, 6) -> List("", "H", "H", "SP", "SP", "SP", "SP", "H", "H", "H", "H"),
    List(5, 5) -> List("", "H", "D", "D", "D", "D", "D", "D", "D", "D", "H"),
    List(4, 4) -> List("", "H", "H", "H", "H", "H", "H", "H", "H", "H", "H"),
    List(3, 3) -> List("", "H", "H", "H", "SP", "SP", "SP", "SP", "H", "H", "H"),
    List(2, 2) -> List("", "H", "H", "H", "SP", "SP", "SP", "SP", "H", "H", "H"))

  // Save the last play
  var lastPlay: Request = _

  /**
   * Processes hit request
   *
   * @param dealer Dealer's mailbox
   * @param upcard Dealer's up-card
   */
  override def hitMe(card: Card) {
    if (curHandIndex > -1) {
      // The player has multiple hands
      debug(this + " playing hand #" + curHandIndex)

      // Play the current hand
      this.hands(curHandIndex).hit(card)
      this.cards = this.hands(curHandIndex).cards
      debug(this + " received a card " + card + " hand sz = " + cards.size + " value = " + value)

      // Upcard is still the same
      play(this.upcard)
    } else {
      // The player only has one hand

      // Hit my hand with this card
      this.hit(card)
      debug(this + " received a card " + card + " hand sz = " + cards.size + " value = " + value)

      // If I've received more than two cards, the extras must be in response to my requests
      if (cards.size > 2)
        play(this.upcard)
    }
  }

  /**
   * Processes the dealer's upcard
   *
   * @param dealer Dealer's mailbox
   * @param upcard Dealer's up-card
   */
  override def play(upcard: Card) {
    actor {
      Thread.sleep(Config.sleepTime)
    }
    this.upcard = upcard

    // Compute my play strategy
    val request = analyze(upcard)

    // Don't send a request if we break or the last play was double-down 
    // since deal will have moved on
    if (this.broke) {
      debug("Player broke!")

    } else {
      if (lastPlay != DoubleDown(this.pid)) {
        debug(this + " sending request to " + request)
        sender ! request
      } else {
        debug(this + " turn ended because last play was double-down")
      }
    }

    // If current hand is broke or player requested stay/double-down, then play the next hand
    if (this.broke || request == Stay(this.pid) || lastPlay == DoubleDown(this.pid)) {
      // Only play if the player still has hands to play
      if (curHandIndex > -1 && (curHandIndex != hands.size - 1)) {
        curHandIndex += 1
      }
    }

    lastPlay = request
  }

  /**
   * Logic behind Basic Strategy used by player
   */
  override def analyze(upcard: Card): Request = {
    
    var action = getBasicStrategy()

    House ! ActionBot(action, tableNo)

    actor {
      Thread.sleep(Config.delayTime)
    }

    // Take action: stay, hit, double-down, or split 
    if (action == "S") {
      return Stay(pid)
    } else if (action == "H") {
      return Hit(pid)
    } else if (action == "D") {
      // Double bet and hit once      
      betAmt = betAmt * 2
      Doubledown = true
      return DoubleDown(pid)
    } else if (action == "SP") {
      return Hit(pid)
    } else {
      return Stay(pid)
    }
  }

  /**
   * Determine the appropriate action based on the basic strategy
   */
  def getBasicStrategy() = {

    // Implement basic strategy
    var action = "?"

    // Determine the appropriate column based on the upcard value
    var dealerCard = 0
    if (upcard.number > 10) {
      // J, K, Q count as 10 in the basic strategy
      dealerCard = 10
    } else {
      dealerCard = upcard.number
    }

    debug(this + " has the following cards = " + cards)

    // Handle pairs and aces separately
    if (cards.contains(Card.ACE)
      || cards(0).number == cards(1).number
      && cards.size < 3) {
      var c0 = if (cards(0).number > 10) 10 else cards(0).number
      val c1 = if (cards(1).number > 10) 10 else cards(1).number

      val unsorted = List(c0, c1)
      val sorted = unsorted.sorted
      action = basicStrategy(sorted)(dealerCard)
    } else {
      if (!this.broke)
        action = basicStrategy(value)(dealerCard)
    }

    // Return action
    action
  }
}